﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

using LongNet.Threading;
using LongNet.Util;
namespace LongNet.Config
{
    internal sealed class FileConfigManagerUsingWatcher<T> : Disposable, IFileConfigManager<T>
         where T : FileConfigEntity, new()
    {
        #region Private Fields

        private static LongNet.Util.LogManager.Logger logger = LongNet.Util.LogManager.GetCurrentClassLogger();

        private T configEntity = null;
        private string configPath = string.Empty;
        private ConfigFileSerializer<T> serializer = null;

        private FileWatcher watcher = null;
        private object syncRoot = new object();

        #endregion

        #region Constructors

        internal FileConfigManagerUsingWatcher(string configPath, T configEntity, ConfigFileType fileType)
        {
            configPath.ThrowsIfNullOrEmpty("configPath", "The config path is null or empty.");

            this.configPath = configPath;
            this.configEntity = configEntity ?? new T();
            this.serializer = new ConfigFileSerializer<T>(fileType);

            // init config entity
            if (!File.Exists(this.configPath))
            {
                Utils.EnsureDirectoryOfFile(this.configPath);
                this.serializer.Save(this.configEntity, this.configPath);
            }
            else
            {
                this.configEntity = this.serializer.Load(this.configPath);
            }

            // start to watch the config file
            watcher = new FileWatcher(configPath, ConfigFileChanged);
            watcher.StartWatching();
        }

        #endregion

        #region Public Events

        public event AppConfigChangedEventHandler ConfigChanged;
        public event AppConfigReloadFailedEventHandler ConfigReloadFailed;

        #endregion

        #region Public Members

        public string ConfigPath
        {
            get
            {
                return configPath;
            }
        }

        public T GetConfig()
        {
            return configEntity;
        }

        public void SaveConfig()
        {
            lock (this.syncRoot)
            {
                this.watcher.StopWatching();
                try
                {
                    Thread.Sleep(10);
                    Utils.EnsureDirectoryOfFile(this.configPath);
                    this.serializer.Save(this.configEntity, this.configPath);

                    this.OnConfigChanged(this, new AppConfigChangedEventArgs(this.configEntity.Clone()));
                }
                finally
                {
                    this.watcher.StartWatching();
                }
            }
        }

        public void BackupConfig(string backupPath)
        {
            this.serializer.Save(this.configEntity, backupPath);
        }

        public void RestoreConfig(string restorePath)
        {
            this.configEntity = this.serializer.Load(restorePath);
            this.SaveConfig();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                lock (this.syncRoot)
                {
                    if (this.watcher != null)
                    {
                        this.watcher.Dispose();
                        this.watcher = null;
                    }
                }
            }
        }

        #endregion

        #region Private Methods

        private void OnConfigChanged(object sender, AppConfigChangedEventArgs e)
        {
            if (this.ConfigChanged != null)
                this.ConfigChanged(sender, e);

            if (logger.IsDebugEnabled)
                logger.Debug("OnConfigChanged, file : {0}", this.configPath);
        }

        private void OnConfigReloadFailed(object sender, AppConfigReloadFailedEventArgs e)
        {
            if (this.ConfigReloadFailed != null)
                this.ConfigReloadFailed(sender, e);

            if (logger.IsDebugEnabled)
                logger.Debug("OnConfigReloadFailed, file : {0}, exception : {1}", this.configPath, e.Exception.Message);
        }

        private void ConfigFileChanged(object sender, FileSystemEventArgs args)
        {
            lock (this.syncRoot)
            {
                try
                {
                    this.watcher.StopWatching();

                    Thread.Sleep(10);
                    this.configEntity = this.serializer.Load(this.configPath);
                    this.OnConfigChanged(this, new AppConfigChangedEventArgs(this.configEntity.Clone()));
                }
                catch (Exception ex)
                {
                    this.OnConfigReloadFailed(this, new AppConfigReloadFailedEventArgs(ex));
                }
                finally
                {
                    this.watcher.StartWatching();
                }
            }
        }

        #endregion
    }
}
